"""
Request routing logic for CodeMCP Gateway.

Routes incoming requests to appropriate language-specific MCP servers
based on file extensions, analysis type, and server availability.
"""

import asyncio
from typing import Dict, Any, List, Optional, Tuple
from pathlib import Path
from enum import Enum

from ..core.config import Config
from ..core.error_handler import handle_error, log_info, log_debug, CodeMCPError
from ..core.base_mcp import BaseMCP


class AnalysisType(Enum):
    """Types of analysis requests."""
    CALL_TREE = "call_tree"
    FUNCTION_INFO = "function_info"
    DEPENDENCIES = "dependencies"
    VALIDATION = "validation"
    PARSE = "parse"
    CALL_CHAIN = "call_chain"
    MANUAL_EXPLORATION = "manual_exploration"


class LanguageType(Enum):
    """Supported languages."""
    PYTHON = "python"
    CPP = "cpp"
    FORTRAN = "fortran"
    TYPESCRIPT = "typescript"
    LATEX = "latex"
    MARKDOWN = "markdown"
    UNKNOWN = "unknown"


class RequestRouter:
    """Routes requests to appropriate MCP servers."""
    
    def __init__(self, config: Config):
        self.config = config
        self.language_extensions = {
            LanguageType.PYTHON: [".py", ".pyw", ".pyi"],
            LanguageType.CPP: [".cpp", ".cc", ".cxx", ".c++", ".c", ".hpp", ".h", ".hxx", ".h++"],
            LanguageType.FORTRAN: [".f", ".f90", ".f95", ".f03", ".f08", ".for", ".ftn"],
            LanguageType.TYPESCRIPT: [".ts", ".tsx", ".js", ".jsx"],
            LanguageType.LATEX: [".tex", ".latex", ".ltx"],
            LanguageType.MARKDOWN: [".md", ".markdown", ".mdown", ".mkd"]
        }
        
        # Reverse mapping for quick lookup
        self.extension_to_language = {}
        for lang, extensions in self.language_extensions.items():
            for ext in extensions:
                self.extension_to_language[ext.lower()] = lang
        
        self.load_balancer = None
        self.is_initialized = False
        
        # Routing statistics
        self.routing_stats = {
            "total_requests": 0,
            "successful_routes": 0,
            "failed_routes": 0,
            "language_distribution": {lang.value: 0 for lang in LanguageType},
            "analysis_type_distribution": {atype.value: 0 for atype in AnalysisType}
        }
    
    async def initialize(self, load_balancer):
        """Initialize the router with load balancer."""
        self.load_balancer = load_balancer
        self.is_initialized = True
        log_info("Request router initialized successfully")
    
    async def shutdown(self):
        """Shutdown the router."""
        self.is_initialized = False
        log_info("Request router shutdown complete")
    
    def detect_language(self, file_path: str) -> LanguageType:
        """
        Detect language from file extension.
        
        Args:
            file_path: Path to the file
            
        Returns:
            Detected language type
        """
        if not file_path:
            return LanguageType.UNKNOWN
        
        file_ext = Path(file_path).suffix.lower()
        return self.extension_to_language.get(file_ext, LanguageType.UNKNOWN)
    
    def validate_analysis_type(self, analysis_type: str) -> AnalysisType:
        """
        Validate and convert analysis type string.
        
        Args:
            analysis_type: Analysis type string
            
        Returns:
            Validated analysis type enum
            
        Raises:
            CodeMCPError: If analysis type is invalid
        """
        try:
            return AnalysisType(analysis_type.lower())
        except ValueError:
            valid_types = [atype.value for atype in AnalysisType]
            raise CodeMCPError(
                f"Invalid analysis type: {analysis_type}. "
                f"Valid types: {', '.join(valid_types)}"
            )
    
    def is_language_enabled(self, language: LanguageType) -> bool:
        """Check if a language is enabled in configuration."""
        if language == LanguageType.PYTHON:
            return self.config.languages.python_enabled
        elif language == LanguageType.CPP:
            return self.config.languages.cpp_enabled
        elif language == LanguageType.FORTRAN:
            return self.config.languages.fortran_enabled
        elif language == LanguageType.TYPESCRIPT:
            return self.config.languages.typescript_enabled
        elif language == LanguageType.LATEX:
            return self.config.documents.latex_enabled
        elif language == LanguageType.MARKDOWN:
            return self.config.documents.markdown_enabled
        else:
            return False
    
    async def route_request(self, 
                           analysis_type: str,
                           file_path: str = None,
                           language: str = None,
                           parameters: Dict[str, Any] = None) -> Tuple[BaseMCP, Dict[str, Any]]:
        """
        Route a request to the appropriate MCP server.
        
        Args:
            analysis_type: Type of analysis to perform
            file_path: Path to file being analyzed (optional)
            language: Override language detection (optional)
            parameters: Additional parameters for the request
            
        Returns:
            Tuple of (MCP server instance, routing metadata)
            
        Raises:
            CodeMCPError: If routing fails
        """
        if not self.is_initialized:
            raise CodeMCPError("Router not initialized")
        
        # Update statistics
        self.routing_stats["total_requests"] += 1
        
        try:
            # Validate analysis type
            analysis_enum = self.validate_analysis_type(analysis_type)
            self.routing_stats["analysis_type_distribution"][analysis_enum.value] += 1
            
            # Determine target language
            if language:
                try:
                    target_language = LanguageType(language.lower())
                except ValueError:
                    raise CodeMCPError(f"Unsupported language: {language}")
            elif file_path:
                target_language = self.detect_language(file_path)
            else:
                # For requests without file context, try to infer from parameters
                target_language = self._infer_language_from_parameters(parameters or {})
            
            # Update language statistics
            self.routing_stats["language_distribution"][target_language.value] += 1
            
            # Check if language is supported and enabled
            if target_language == LanguageType.UNKNOWN:
                raise CodeMCPError(
                    f"Could not determine language for request. "
                    f"File path: {file_path}, Language: {language}"
                )
            
            if not self.is_language_enabled(target_language):
                raise CodeMCPError(f"Language {target_language.value} is not enabled")
            
            # Get MCP server from load balancer
            mcp_server = await self.load_balancer.get_server(target_language.value)
            
            if not mcp_server:
                raise CodeMCPError(f"No available MCP server for language: {target_language.value}")
            
            # Validate that the server can handle the request
            if file_path:
                validation_result = await mcp_server.validate_file(file_path)
                if not validation_result.get("valid", False):
                    reason = validation_result.get("reason", "Unknown validation error")
                    raise CodeMCPError(f"Server cannot process file: {reason}")
            
            # Create routing metadata
            routing_metadata = {
                "target_language": target_language.value,
                "analysis_type": analysis_enum.value,
                "mcp_server_name": mcp_server.name,
                "file_path": file_path,
                "routing_time": asyncio.get_event_loop().time(),
                "load_balancer_choice": self.load_balancer.get_server_selection_info(target_language.value)
            }
            
            self.routing_stats["successful_routes"] += 1
            
            log_debug(f"Routed {analysis_type} request for {target_language.value} to {mcp_server.name}")
            
            return mcp_server, routing_metadata
            
        except Exception as e:
            self.routing_stats["failed_routes"] += 1
            
            if isinstance(e, CodeMCPError):
                raise
            else:
                error_response = handle_error(e, {
                    "analysis_type": analysis_type,
                    "file_path": file_path,
                    "language": language,
                    "parameters": parameters
                })
                raise CodeMCPError(f"Routing failed: {error_response['message']}")
    
    def _infer_language_from_parameters(self, parameters: Dict[str, Any]) -> LanguageType:
        """
        Attempt to infer language from request parameters.
        
        Args:
            parameters: Request parameters
            
        Returns:
            Inferred language type
        """
        # Look for language hints in parameters
        if "language" in parameters:
            try:
                return LanguageType(parameters["language"].lower())
            except ValueError:
                pass
        
        # Look for file extension in function signatures or similar
        if "signature" in parameters:
            signature = parameters["signature"].lower()
            if "def " in signature or "import " in signature:
                return LanguageType.PYTHON
            elif "#include" in signature or "int main" in signature:
                return LanguageType.CPP
            elif "function " in signature or "=>" in signature:
                return LanguageType.TYPESCRIPT
            elif "subroutine" in signature or "program " in signature:
                return LanguageType.FORTRAN
        
        # Default to unknown
        return LanguageType.UNKNOWN
    
    async def find_alternative_server(self, 
                                    original_language: str, 
                                    analysis_type: str) -> Optional[BaseMCP]:
        """
        Find an alternative server if the primary choice fails.
        
        Args:
            original_language: The originally requested language
            analysis_type: Type of analysis
            
        Returns:
            Alternative MCP server or None
        """
        # For some analysis types, we might be able to use a different language server
        # For example, dependency analysis might work across languages
        
        if analysis_type in ["validation", "parse"]:
            # These typically require the specific language parser
            return None
        
        # Try to find any available server as fallback
        available_languages = []
        for lang in LanguageType:
            if lang != LanguageType.UNKNOWN and self.is_language_enabled(lang):
                server = await self.load_balancer.get_server(lang.value, check_health=True)
                if server:
                    available_languages.append(lang)
        
        if available_languages:
            # Return the first available server
            fallback_lang = available_languages[0]
            return await self.load_balancer.get_server(fallback_lang.value)
        
        return None
    
    def get_supported_languages(self) -> List[str]:
        """Get list of supported and enabled languages."""
        supported = []
        for lang in LanguageType:
            if lang != LanguageType.UNKNOWN and self.is_language_enabled(lang):
                supported.append(lang.value)
        return supported
    
    def get_supported_analysis_types(self) -> List[str]:
        """Get list of supported analysis types."""
        return [atype.value for atype in AnalysisType]
    
    def get_file_extensions(self, language: str = None) -> Dict[str, List[str]]:
        """
        Get supported file extensions.
        
        Args:
            language: Specific language to get extensions for
            
        Returns:
            Dictionary mapping languages to their extensions
        """
        if language:
            try:
                lang_enum = LanguageType(language.lower())
                return {language: self.language_extensions.get(lang_enum, [])}
            except ValueError:
                return {}
        
        # Return all extensions
        result = {}
        for lang, extensions in self.language_extensions.items():
            if self.is_language_enabled(lang):
                result[lang.value] = extensions
        
        return result
    
    def get_routing_statistics(self) -> Dict[str, Any]:
        """Get routing statistics."""
        stats = self.routing_stats.copy()
        
        # Calculate success rate
        total = stats["total_requests"]
        if total > 0:
            stats["success_rate"] = stats["successful_routes"] / total * 100
            stats["failure_rate"] = stats["failed_routes"] / total * 100
        else:
            stats["success_rate"] = 0.0
            stats["failure_rate"] = 0.0
        
        return stats
    
    def reset_statistics(self):
        """Reset routing statistics."""
        self.routing_stats = {
            "total_requests": 0,
            "successful_routes": 0,
            "failed_routes": 0,
            "language_distribution": {lang.value: 0 for lang in LanguageType},
            "analysis_type_distribution": {atype.value: 0 for atype in AnalysisType}
        }
        log_info("Routing statistics reset")
    
    async def health_check(self) -> Dict[str, Any]:
        """Perform health check on the router."""
        return {
            "router_initialized": self.is_initialized,
            "load_balancer_available": self.load_balancer is not None,
            "supported_languages": self.get_supported_languages(),
            "supported_analysis_types": self.get_supported_analysis_types(),
            "routing_statistics": self.get_routing_statistics()
        }